#include "DrawSystem.h"
#include "DrawObject.h"
#include "FontManager.h"
#include "../tools/Log.h"
#include "../tools/Time.h"
#include "../control/ControlSystem.h"

#include <assert.h>

#include <GL/glfw.h>

// Render each object
void DrawSystem::update(long dt)
{
// Start fresh
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glLoadIdentity();
	
	if(orthoObjects.empty() && modelObjects.empty()) {
// No objects to draw, so draw a default message saying so
		glPushMatrix();
			glColor3d(0,1,1);
			glTranslated(2, 2, BACKGROUND);
			FTFont* f = FontManager::Instance().getFont("FreeSans.ttf",8);
			if(f != NULL) {
				f->Render("No objects to draw...");
			}
		glPopMatrix();
	} else {
// Draw everything
		setModelView();
	
		//calculate
		fVec2D lookAtAng = cameraLookAt;
		float mag = lookAtAng.magnitude();
		float ang = std::atan2(lookAtAng.y, lookAtAng.x);
		lookAtAng.normalize();
		if(cameraPosSpeed.magnitudeSquared() > 0) {
// Move the camera in the direction that we're looking, but only on the x/y plane
			fVec2D diffVec(lookAtAng);
			diffVec *= cameraPosSpeed.x * dt;
			cameraPosition += lookAtAng * cameraPosSpeed.x * dt;
// Move the camera in a strafe manner based on the y speed
			float rotAng = ang - 3.1415926/2;
			diffVec = fVec2D(std::cos(rotAng), std::sin(rotAng));
			cameraPosition += diffVec * cameraPosSpeed.y * dt;
		}
	
		if(cameraRotSpeed.y != 0) {
// only change the rotation about the y axis because that's the only look at rotation we allow
			ang += cameraRotSpeed.y * dt;
			fVec2D newLookAt(std::cos(ang), std::sin(ang));
			newLookAt = newLookAt.normalized() * mag;
		
			cameraLookAt = newLookAt;
		}
	
		gluLookAt(cameraPosition.x, cameraPosition.y, cameraPosition.z,
				cameraPosition.x + cameraLookAt.x, cameraPosition.y + cameraLookAt.y, cameraPosition.z + cameraLookAt.z,
				cameraUp.x, cameraUp.y, cameraUp.z);
// Draw all 3D objects
		set<ModelObject*>::const_iterator m_itr;
		for(m_itr = modelObjects.begin(); m_itr != modelObjects.end(); m_itr++) {
			glPushMatrix();
				(*m_itr)->draw(dt);
			glPopMatrix();
		}

// Shift over the 2D drawing
		glLoadIdentity();
		setOrthoView();
	
		glEnable(GL_CULL_FACE);	//Don't render the back faces because no one will ever see them
// Draw all 2D objects
		set<OrthoObject*>::const_iterator o_itr;
		for(o_itr = orthoObjects.begin(); o_itr != orthoObjects.end(); o_itr++) {
			glPushMatrix();
				(*o_itr)->draw(dt);
			glPopMatrix();
		}
		glDisable(GL_CULL_FACE);
	}
	glFlush();
	glfwSwapBuffers();
}

void DrawSystem::setOrthoView() {
// This hardcodes the frame dimensions: width [0, aspectRatio*100]	height [0, 100]
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glOrtho( 0, aspectRatio*100 , 0, 100, 0, -100 );
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
}

void DrawSystem::setModelView() {
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(FoV, aspectRatio, 1000, 100000);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
}

void resizeWindow(int width, int height) {
	DrawSystem& drawSystem = DrawSystem::Instance();
	drawSystem.windowWidth = width;
	drawSystem.windowHeight = height;
	glViewport(0, 0, width, height);
	drawSystem.aspectRatio = double(width)/height;
	drawSystem.frameHeight = 100;
	drawSystem.frameWidth = drawSystem.aspectRatio * drawSystem.frameHeight;
	
	ControlSystem::Instance().onWindowResize(drawSystem.frameWidth, drawSystem.frameHeight);
}

bool DrawSystem::init(const CmdLineArgs&)
{
	glfwGetWindowSize(&windowWidth, &windowHeight);
	glfwSetWindowSizeCallback(&resizeWindow);

	glEnable(GL_DEPTH_TEST);
	glEnable(GL_CULL_FACE);
	glEnable(GL_BLEND);
	glDisable(GL_NORMALIZE);
	
	glFrontFace(GL_CCW);
	glPolygonMode(GL_FRONT, GL_FILL);
	glShadeModel(GL_SMOOTH);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	
	glClearColor(0,0,0,0);
	glPointSize(2.0f);
	
	return true;
}

bool DrawSystem::addOrthoObject(OrthoObject* o) { // TODO ERROR NOT THREAD SAFE
	assert(o != NULL);
	if(orthoObjects.find(o) == orthoObjects.end()) {
		orthoObjects.insert(o);
		return true;
	}
	return false;
}

bool DrawSystem::removeOrthoObject(OrthoObject* o) { // TODO ERROR NOT THREAD SAFE
	assert(o != NULL);
	set<OrthoObject*>::iterator itr;
	if((itr = orthoObjects.find(o)) == orthoObjects.end())
		return false;
	
	orthoObjects.erase(itr);
	return true;
}
	
bool DrawSystem::addModelObject(ModelObject* o) { // TODO ERROR NOT THREAD SAFE
	assert(o != NULL);
	if(modelObjects.find(o) == modelObjects.end()) {
		modelObjects.insert(o);
		return true;
	}
	return false;
}

bool DrawSystem::removeModelObject(ModelObject* o) { // TODO ERROR NOT THREAD SAFE
	assert(o != NULL);
	set<ModelObject*>::iterator itr;
	if((itr = modelObjects.find(o)) == modelObjects.end())
		return false;
	
	modelObjects.erase(itr);
	return true;
}

void DrawSystem::kill() {
	terminate();
}

void DrawSystem::terminate() {
	if(!orthoObjects.empty()) {
		lout<<"WARNING: DrawSystem - Shame on you, you leaked "<<orthoObjects.size()<<" ortho object(s)"<<endl;
		for(set<OrthoObject*>::iterator itr = orthoObjects.begin(); itr != orthoObjects.end(); itr++)
			lout <<"Pos ("<<(*itr)->drawPos.x<<", "<<(*itr)->drawPos.y<<", "<<(*itr)->drawPos.z<<")  Color ("
				<<(*itr)->color.r<<", "<<(*itr)->color.g<<", "<<(*itr)->color.b<<")"<<endl;
	}
	if(!modelObjects.empty()) {
		lout<<"WARNING: DrawSystem - Shame on you, you leaked "<<modelObjects.size()<<" model object(s)"<<endl;
		for(set<ModelObject*>::iterator itr = modelObjects.begin(); itr != modelObjects.end(); itr++)
			lout <<"Pos ("<<(*itr)->drawPos.x<<", "<<(*itr)->drawPos.y<<", "<<(*itr)->drawPos.z<<")  Color ("
				<<(*itr)->color.r<<", "<<(*itr)->color.g<<", "<<(*itr)->color.b<<")"<<endl;
	}
}

extern const unsigned long SUBSPACE_RESOLUTION;

DrawSystem::DrawSystem() :
  orthoObjects()
, modelObjects()
, cameraUp(0,0,1)
, windowWidth(0)
, windowHeight(0)
, frameWidth(0)
, frameHeight(0)
, FoV(40)
, aspectRatio(0)
, cameraPosition(2.5 * SUBSPACE_RESOLUTION ,-1.5 * SUBSPACE_RESOLUTION,5 * SUBSPACE_RESOLUTION)
, cameraPosSpeed(0,0,0)
, cameraLookAt(0, 1, -1)
, cameraRotSpeed(0,0,0)
{
	cameraLookAt.normalize();
}

